"""
Deployment Manager for WebService

Handles creation and management of Kubernetes Deployment resources.
"""

import logging
from typing import Optional, Dict, Any
from kubernetes import client
from kubernetes.client.rest import ApiException

from .api import WebServiceSpec, DeploymentStatus
from .resource_manager import ResourceManager
from common import consts


class DeploymentManagerError(Exception):
    """Deployment manager error"""
    pass


class DeploymentManager:
    """Kubernetes Deployment resource management"""

    def __init__(self):
        """Initialize deployment manager"""
        self.logger = logging.getLogger(__name__)
        self.apps_v1 = client.AppsV1Api()
        self.v1 = client.CoreV1Api()
        self.resource_manager = ResourceManager()

    def create_deployment(self, webservice_spec: WebServiceSpec, webservice_name: str,
                          webservice_namespace: str) -> str:
        """
        Create Deployment resource
        
        Args:
            webservice_spec: WebService specification object
            webservice_name: Name of the WebService for reference
            webservice_namespace: Namespace of the WebService
            
        Returns:
            Name of the created Deployment resource
            
        Raises:
            DeploymentManagerError: If deployment creation fails
        """
        try:
            deployment_name = self._generate_deployment_name(webservice_name)

            self.logger.info(f"Creating deployment {deployment_name} for WebService {webservice_name}")

            # Create deployment object
            deployment = self._build_deployment_object(
                webservice_spec, deployment_name, webservice_name, webservice_namespace
            )

            # Create deployment in Kubernetes
            created_deployment = self.apps_v1.create_namespaced_deployment(
                namespace=webservice_namespace,
                body=deployment
            )

            self.logger.info(f"Successfully created deployment {deployment_name}")
            return deployment_name

        except ApiException as e:
            error_msg = f"Failed to create deployment {deployment_name}: {e}"
            self.logger.error(error_msg)
            raise DeploymentManagerError(error_msg) from e
        except Exception as e:
            error_msg = f"Unexpected error creating deployment {deployment_name}: {e}"
            self.logger.error(error_msg)
            raise DeploymentManagerError(error_msg) from e

    def update_deployment(self, webservice_spec: WebServiceSpec, webservice_name: str,
                          webservice_namespace: str) -> str:
        """
        Update existing Deployment resource
        
        Args:
            webservice_spec: WebService specification object
            webservice_name: Name of the WebService for reference
            webservice_namespace: Namespace of the WebService
            
        Returns:
            Name of the updated Deployment resource
            
        Raises:
            DeploymentManagerError: If deployment update fails
        """
        try:
            deployment_name = self._generate_deployment_name(webservice_name)

            self.logger.info(f"Updating deployment {deployment_name} for WebService {webservice_name}")

            # Get existing deployment
            existing_deployment = self.apps_v1.read_namespaced_deployment(
                name=deployment_name,
                namespace=webservice_namespace
            )

            # Build new deployment object
            new_deployment = self._build_deployment_object(
                webservice_spec, deployment_name, webservice_name, webservice_namespace
            )

            # Update deployment in Kubernetes
            updated_deployment = self.apps_v1.patch_namespaced_deployment(
                name=deployment_name,
                namespace=webservice_namespace,
                body=new_deployment
            )

            self.logger.info(f"Successfully updated deployment {deployment_name}")
            return deployment_name

        except ApiException as e:
            error_msg = f"Failed to update deployment {deployment_name}: {e}"
            self.logger.error(error_msg)
            raise DeploymentManagerError(error_msg) from e
        except Exception as e:
            error_msg = f"Unexpected error updating deployment {deployment_name}: {e}"
            self.logger.error(error_msg)
            raise DeploymentManagerError(error_msg) from e

    def delete_deployment(self, webservice_name: str, webservice_namespace: str) -> bool:
        """
        Delete Deployment resource
        
        Args:
            webservice_name: Name of the WebService for reference
            webservice_namespace: Namespace of the WebService
            
        Returns:
            True if deletion was successful
            
        Raises:
            DeploymentManagerError: If deployment deletion fails
        """
        try:
            deployment_name = self._generate_deployment_name(webservice_name)

            self.logger.info(f"Deleting deployment {deployment_name} for WebService {webservice_name}")

            # Delete deployment in Kubernetes
            self.apps_v1.delete_namespaced_deployment(
                name=deployment_name,
                namespace=webservice_namespace
            )

            self.logger.info(f"Successfully deleted deployment {deployment_name}")
            return True

        except ApiException as e:
            if e.status == 404:
                self.logger.info(f"Deployment {deployment_name} not found, already deleted")
                return True
            error_msg = f"Failed to delete deployment {deployment_name}: {e}"
            self.logger.error(error_msg)
            raise DeploymentManagerError(error_msg) from e
        except Exception as e:
            error_msg = f"Unexpected error deleting deployment {deployment_name}: {e}"
            self.logger.error(error_msg)
            raise DeploymentManagerError(error_msg) from e

    def get_deployment_status(self, webservice_name: str, webservice_namespace: str) -> str:
        """Get Deployment status"""
        try:
            deployment_name = self._generate_deployment_name(webservice_name)
            deployment = self.apps_v1.read_namespaced_deployment(
                name=deployment_name,
                namespace=webservice_namespace
            )

            status = deployment.status
            if not status or status.replicas == 0:
                return DeploymentStatus.PENDING.value

            # Check if all replicas are ready and available
            if (status.available_replicas == status.replicas and
                    status.ready_replicas == status.replicas):
                return DeploymentStatus.HEALTHY.value

            return DeploymentStatus.PENDING.value

        except ApiException as e:
            return DeploymentStatus.UNHEALTHY.value if e.status == 404 else DeploymentStatus.UNKNOWN.value
        except Exception as e:
            self.logger.error(f"Error getting deployment status: {e}")
            return DeploymentStatus.UNKNOWN.value

    def _generate_deployment_name(self, webservice_name: str) -> str:
        """Generate deployment name from WebService name"""
        return f"{webservice_name}-deployment"

    def _build_probe_dict(self, probe) -> Dict[str, Any]:
        """Build probe dictionary from probe object"""
        probe_dict = {}
        if probe.http_get:
            probe_dict["httpGet"] = {
                "path": probe.http_get.path,
                "port": probe.http_get.port
            }
        if probe.initial_delay_seconds is not None:
            probe_dict["initialDelaySeconds"] = probe.initial_delay_seconds
        if probe.period_seconds is not None:
            probe_dict["periodSeconds"] = probe.period_seconds
        if probe.timeout_seconds is not None:
            probe_dict["timeoutSeconds"] = probe.timeout_seconds
        if probe.failure_threshold is not None:
            probe_dict["failureThreshold"] = probe.failure_threshold
        return probe_dict

    def _build_deployment_object(self, webservice_spec: WebServiceSpec, deployment_name: str,
                                 webservice_name: str, webservice_namespace: str) -> Dict:
        """Build Kubernetes Deployment object"""
        deployment_spec = webservice_spec.deployment

        # Build container specs
        containers = []
        for container in deployment_spec.containers:
            container_dict = {
                "name": container.name,
                "image": container.image,
                "imagePullPolicy": container.image_pull_policy.value if container.image_pull_policy else "IfNotPresent",
            }

            # Add ports
            if container.ports:
                container_dict["ports"] = [
                    {"containerPort": port.container_port}
                    for port in container.ports
                ]

            # Add probes
            if container.liveness_probe:
                container_dict["livenessProbe"] = self._build_probe_dict(container.liveness_probe)
            if container.readiness_probe:
                container_dict["readinessProbe"] = self._build_probe_dict(container.readiness_probe)

            # Add resource limits and requests based on computeUnit
            self._add_resource_limits(container_dict, webservice_spec, deployment_name,
                                      deployment_spec.replicas)

            containers.append(container_dict)

        # Build deployment object
        deployment = {
            "apiVersion": "apps/v1",
            "kind": "Deployment",
            "metadata": {
                "name": deployment_name,
                "namespace": webservice_namespace,
                "labels": {
                    "app": webservice_name,
                    "webservice": webservice_name,
                    "managed-by": consts.APPLICATION_NAME
                }
            },
            "spec": {
                "replicas": deployment_spec.replicas,
                "selector": {
                    "matchLabels": {
                        "app": webservice_name,
                        "webservice": webservice_name
                    }
                },
                "template": {
                    "metadata": {
                        "labels": {
                            "app": webservice_name,
                            "webservice": webservice_name
                        }
                    },
                    "spec": {
                        "containers": containers
                    }
                }
            }
        }

        return deployment


    def _add_resource_limits(self, container_dict: Dict[str, Any], webservice_spec: WebServiceSpec,
                             deployment_name: str, replicas: int) -> None:
        """
        Add resource limits and requests to container based on computeUnit
        
        Args:
            container_dict: Container configuration dictionary
            webservice_spec: WebService specification
            deployment_name: Name of the deployment
            replicas: Number of replicas in this deployment
        """
        try:
            # Get computeUnit from webservice spec
            compute_unit = webservice_spec.quota.compute_unit if webservice_spec.quota else 1

            # Calculate resources for this deployment
            pod_resources = self.resource_manager.calculate_pod_resources(compute_unit, replicas)

            # Add resources to container
            container_dict["resources"] = {
                "requests": pod_resources["requests"],
                "limits": pod_resources["limits"]
            }

            self.logger.debug(f"Added resources to container {container_dict['name']}: {pod_resources}")

        except Exception as e:
            self.logger.warning(f"Failed to calculate resources for container {container_dict.get('name', 'unknown')} "
                                f"in deployment {deployment_name}: {e}")
            # Don't fail the deployment creation if resource calculation fails
            # Just log a warning and continue without resource limits
